//-----------------------------------------------------------------------------
//   File:      fw.c
//   Contents:   Firmware frameworks task dispatcher and device request parser
//            source.
//
// indent 3.  NO TABS!
//
//
// Copyright (c) 2011, Cypress Semiconductor Corporation All rights reserved
//-----------------------------------------------------------------------------
#include "fx2.h"
#include "fx2regs.h"

//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
#define DELAY_COUNT 0x9248 * 8L // Delay for 8 sec at 24Mhz, 4 sec at 48
#define _IFREQ 48000            // IFCLK constant for Synchronization Delay
#define _CFREQ 48000            // CLKOUT constant for Synchronization Delay

//-----------------------------------------------------------------------------
// Random Macros
//-----------------------------------------------------------------------------
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))

// Registers which require a synchronization delay, see section 15.14
// FIFORESET        FIFOPINPOLAR
// INPKTEND         OUTPKTEND
// EPxBCH:L         REVCTL
// GPIFTCB3         GPIFTCB2
// GPIFTCB1         GPIFTCB0
// EPxFIFOPFH:L     EPxAUTOINLENH:L
// EPxFIFOCFG       EPxGPIFFLGSEL
// PINFLAGSxx       EPxFIFOIRQ
// EPxFIFOIE        GPIFIRQ
// GPIFIE           GPIFADRH:L
// UDMACRCH:L       EPxGPIFTRIG
// GPIFTRIG

// Note: The pre-REVE EPxGPIFTCH/L register are affected, as well...
//      ...these have been replaced by GPIFTC[B3:B0] registers

#include "fx2sdly.h" // Define _IFREQ and _CFREQ above this #include

//-----------------------------------------------------------------------------
// Global Variables
//-----------------------------------------------------------------------------
volatile BOOL GotSUD;
BOOL Rwuen;
BOOL Selfpwr;
volatile BOOL Sleep; // Sleep mode enable flag

WORD pDeviceDscr; // Pointer to Device Descriptor; Descriptors may be moved
WORD pDeviceQualDscr;
WORD pHighSpeedConfigDscr;
WORD pFullSpeedConfigDscr;
WORD pConfigDscr;
WORD pOtherConfigDscr;
WORD pStringDscr;

//-----------------------------------------------------------------------------
// Prototypes
//-----------------------------------------------------------------------------
void SetupCommand(void);
void TD_Init(void);
void TD_Poll(void);
BOOL TD_Suspend(void);
BOOL TD_Resume(void);

BOOL DR_GetDescriptor(void);
BOOL DR_SetConfiguration(void);
BOOL DR_GetConfiguration(void);
BOOL DR_SetInterface(void);
BOOL DR_GetInterface(void);
BOOL DR_GetStatus(void);
BOOL DR_ClearFeature(void);
BOOL DR_SetFeature(void);
BOOL DR_VendorCmnd(void);

// this table is used by the epcs macro
const char code EPCS_Offset_Lookup_Table[] =
    {
        0, // EP1OUT
        1, // EP1IN
        2, // EP2OUT
        2, // EP2IN
        3, // EP4OUT
        3, // EP4IN
        4, // EP6OUT
        4, // EP6IN
        5, // EP8OUT
        5, // EP8IN
};

// macro for generating the address of an endpoint's control and status register (EPnCS)
#define epcs(EP) (EPCS_Offset_Lookup_Table[(EP & 0x7E) | (EP > 128)] + 0xE6A1)

//-----------------------------------------------------------------------------
// Code
//-----------------------------------------------------------------------------

// Task dispatcher
void main(void)
{
   DWORD i;
   WORD offset;
   DWORD DevDescrLen;
   DWORD j = 0;
   WORD IntDescrAddr;
   WORD ExtDescrAddr;

   // Initialize Global States
   Sleep = FALSE;   // Disable sleep mode
   Rwuen = FALSE;   // Disable remote wakeup
   Selfpwr = FALSE; // Disable self powered
   GotSUD = FALSE;  // Clear "Got setup data" flag

   // Initialize user device
   TD_Init();

   // The following section of code is used to relocate the descriptor table.
   // Since the SUDPTRH and SUDPTRL are assigned the address of the descriptor
   // table, the descriptor table must be located in on-part memory.
   // The 4K demo tools locate all code sections in external memory.
   // The descriptor table is relocated by the frameworks ONLY if it is found
   // to be located in external memory.
   pDeviceDscr = (WORD)&DeviceDscr;
   pDeviceQualDscr = (WORD)&DeviceQualDscr;
   pHighSpeedConfigDscr = (WORD)&HighSpeedConfigDscr;
   pFullSpeedConfigDscr = (WORD)&FullSpeedConfigDscr;
   pStringDscr = (WORD)&StringDscr;

   if ((WORD)&DeviceDscr & 0xe000)
   {
      IntDescrAddr = INTERNAL_DSCR_ADDR;
      ExtDescrAddr = (WORD)&DeviceDscr;
      DevDescrLen = (WORD)&UserDscr - (WORD)&DeviceDscr + 2;
      for (i = 0; i < DevDescrLen; i++)
         *((BYTE xdata *)IntDescrAddr + i) = 0xCD;
      for (i = 0; i < DevDescrLen; i++)
         *((BYTE xdata *)IntDescrAddr + i) = *((BYTE xdata *)ExtDescrAddr + i);
      pDeviceDscr = IntDescrAddr;
      offset = (WORD)&DeviceDscr - INTERNAL_DSCR_ADDR;
      pDeviceQualDscr -= offset;
      pConfigDscr -= offset;
      pOtherConfigDscr -= offset;
      pHighSpeedConfigDscr -= offset;
      pFullSpeedConfigDscr -= offset;
      pStringDscr -= offset;
   }

   EZUSB_IRQ_ENABLE();    // Enable USB interrupt (INT2)
   EZUSB_ENABLE_RSMIRQ(); // Wake-up interrupt

   // What is INT2 is for USB & INT4 is for the Slave FIFOs
   INTSETUP |= (bmAV2EN | bmAV4EN); // Enable INT 2 & 4 autovectoring

   // I don't think we care about Setup PIDs only the Setup data; commented out
   // bmSUTOK but we want bmSUDAV.
   USBIE |= bmSUDAV | bmSUTOK | bmSUSP | bmURES | bmHSGRANT; // Enable selected interrupts

   // Global interrupt enable. Controls masking of all interrupts except USB wakeup
   // (resume). EA = 0 disables all interrupts except USB wakeup. When EA = 1, interrupts are
   // enabled or masked by their individual enable bits.
   EA = 1; // Enable 8051 interrupts

#ifndef NO_RENUM
   // Renumerate if necessary.  Do this by checking the renum bit.  If it
   // is already set, there is no need to renumerate.  The renum bit will
   // already be set if this firmware was loaded from an eeprom.
   if (!(USBCS & bmRENUM))
   {
      EZUSB_Discon(TRUE); // renumerate
   }
#endif

   // unconditionally re-connect.  If we loaded from eeprom we are
   // disconnected and need to connect.  If we just renumerated this
   // is not necessary but doesn't hurt anything
   USBCS &= ~bmDISCON;

   // The three LSBs of the Clock Control Register (CKCON, at SFR location 0x8E) control the stretch
   // value; stretch values between zero and seven may be used. A stretch value of zero adds zero
   // instruction cycles, resulting in MOVX instructions which execute in two instruction cycles.
   CKCON = (CKCON & (~bmSTRETCH)) | FW_STRETCH_VALUE; // Set stretch to 0 (after renumeration)

   // clear the Sleep flag.
   Sleep = FALSE;
   EZUSB_InitI2C(); // Initialize EZ-USB I2C controller

   // Task Dispatcher
   while (TRUE) // Main Loop
   {
      if (GotSUD) // Wait for SUDAV
      {
         SetupCommand(); // Implement setup command
         GotSUD = FALSE; // Clear SUDAV flag
      }

      // Poll User Device
      // NOTE: Idle mode stops the processor clock.  There are only two
      // ways out of idle mode, the WAKEUP pin, and detection of the USB
      // resume state on the USB bus.  The timers will stop and the
      // processor will not wake up on any other interrupts.
      if (Sleep)
      {
         if (TD_Suspend())
         {
            Sleep = FALSE; // Clear the "go to sleep" flag.  Do it here to prevent any race condition between wakeup and the next sleep.
            do
            {
               EZUSB_Susp(); // Place processor in idle mode.
            } while (!Rwuen && EZUSB_EXTWAKEUP());
            // Must continue to go back into suspend if the host has disabled remote wakeup
            // *and* the wakeup was caused by the external wakeup pin.

            // 8051 activity will resume here due to USB bus or Wakeup# pin activity.
            EZUSB_Resume(); // If source is the Wakeup# pin, signal the host to Resume.
            TD_Resume();
         }
      }
      TD_Poll();
   }
}

// Device request parser
void SetupCommand(void)
{
   void *dscr_ptr;

   switch (SETUPDAT[1])
   {
   case SC_GET_DESCRIPTOR: // *** Get Descriptor
      if (DR_GetDescriptor())
         switch (SETUPDAT[3])
         {
         case GD_DEVICE: // Device
            SUDPTRH = MSB(pDeviceDscr);
            SUDPTRL = LSB(pDeviceDscr);
            break;
         case GD_DEVICE_QUALIFIER: // Device Qualifier
            SUDPTRH = MSB(pDeviceQualDscr);
            SUDPTRL = LSB(pDeviceQualDscr);
            break;
         case GD_CONFIGURATION: // Configuration
            SUDPTRH = MSB(pConfigDscr);
            SUDPTRL = LSB(pConfigDscr);
            break;
         case GD_OTHER_SPEED_CONFIGURATION: // Other Speed Configuration
            SUDPTRH = MSB(pOtherConfigDscr);
            SUDPTRL = LSB(pOtherConfigDscr);
            break;
         case GD_STRING: // String
            if (dscr_ptr = (void *)EZUSB_GetStringDscr(SETUPDAT[2]))
            {
               SUDPTRH = MSB(dscr_ptr);
               SUDPTRL = LSB(dscr_ptr);
            }
            else
               EZUSB_STALL_EP0(); // Stall End Point 0
            break;
         default:              // Invalid request
            EZUSB_STALL_EP0(); // Stall End Point 0
         }
      break;
   case SC_GET_INTERFACE: // *** Get Interface
      DR_GetInterface();
      break;
   case SC_SET_INTERFACE: // *** Set Interface
      DR_SetInterface();
      break;
   case SC_SET_CONFIGURATION: // *** Set Configuration
      DR_SetConfiguration();
      break;
   case SC_GET_CONFIGURATION: // *** Get Configuration
      DR_GetConfiguration();
      break;
   case SC_GET_STATUS: // *** Get Status
      if (DR_GetStatus())
         switch (SETUPDAT[0])
         {
         case GS_DEVICE: // Device
            EP0BUF[0] = ((BYTE)Rwuen << 1) | (BYTE)Selfpwr;
            EP0BUF[1] = 0;
            EP0BCH = 0;
            EP0BCL = 2;
            break;
         case GS_INTERFACE: // Interface
            EP0BUF[0] = 0;
            EP0BUF[1] = 0;
            EP0BCH = 0;
            EP0BCL = 2;
            break;
         case GS_ENDPOINT: // End Point
            EP0BUF[0] = *(BYTE xdata *)epcs(SETUPDAT[4]) & bmEPSTALL;
            EP0BUF[1] = 0;
            EP0BCH = 0;
            EP0BCL = 2;
            break;
         default:              // Invalid Command
            EZUSB_STALL_EP0(); // Stall End Point 0
         }
      break;
   case SC_CLEAR_FEATURE: // *** Clear Feature
      if (DR_ClearFeature())
         switch (SETUPDAT[0])
         {
         case FT_DEVICE: // Device
            if (SETUPDAT[2] == 1)
               Rwuen = FALSE; // Disable Remote Wakeup
            else
               EZUSB_STALL_EP0(); // Stall End Point 0
            break;
         case FT_ENDPOINT: // End Point
            if (SETUPDAT[2] == 0)
            {
               *(BYTE xdata *)epcs(SETUPDAT[4]) &= ~bmEPSTALL;
               EZUSB_RESET_DATA_TOGGLE(SETUPDAT[4]);
            }
            else
               EZUSB_STALL_EP0(); // Stall End Point 0
            break;
         }
      break;
   case SC_SET_FEATURE: // *** Set Feature
      if (DR_SetFeature())
         switch (SETUPDAT[0])
         {
         case FT_DEVICE: // Device
            if (SETUPDAT[2] == 1)
               Rwuen = TRUE; // Enable Remote Wakeup
            else if (SETUPDAT[2] == 2)
               // Set Feature Test Mode.  The core handles this request.  However, it is
               // necessary for the firmware to complete the handshake phase of the
               // control transfer before the chip will enter test mode.  It is also
               // necessary for FX2 to be physically disconnected (D+ and D-)
               // from the host before it will enter test mode.
               break;
            else
               EZUSB_STALL_EP0(); // Stall End Point 0
            break;
         case FT_ENDPOINT: // End Point
            *(BYTE xdata *)epcs(SETUPDAT[4]) |= bmEPSTALL;
            break;
         }
      break;
   default: // *** Invalid Command
      if (DR_VendorCmnd())
         EZUSB_STALL_EP0(); // Stall End Point 0
   }

   // Acknowledge handshake phase of device request
   EP0CS |= bmHSNAK;
}

// Wake-up interrupt handler
void resume_isr(void) interrupt WKUP_VECT
{
   EZUSB_CLEAR_RSMIRQ();
}
